PythonでLZHファイルを展開してみる
はじめに
データアナリティクス事業本部のkobayashiです。
今は昔アーカイバとして頻繁に使われていましたLZHファイルですが、脆弱性の問題から使用が非推奨となり現在ではほぼ使用されていないです。ただ今回LZHのファイルをPythonで展開する機会がありPythonでLZHファイルを扱う方法を調べましたのでその内容をまとめます。
環境
- macOS 10.15.7
- Python 3.7.12
PythonでのLZH解凍方法
LZHは現在ほぼ使われていない圧縮形式のため展開する手段もあまり見つからなかったので当初はlhaをインストールしてsubprocessから使う方法を考えていましたが継続して調査していたところ Lhafile というPythonのモジュールが一つだけ見つかりました。
上記の様な経緯から以下の二つの方法を紹介したいと思います。
- subprocessからlhaを呼び出す
- Lhafileモジュールで操作する
なお今回扱うLZHファイルの中身ですが以下の様な郵便番号のCSVファイルが圧縮されているファイルとなります。
zipcode.lzh ├── 08IBARAK.CSV ├── 09TOCHIG.CSV ├── 10GUMMA.CSV ├── 11SAITAM.CSV ├── 12CHIBA.CSV ├── 13TOKYO.CSV └── 14KANAGA.CSV
subprocessからlhaを呼び出す
Pythonのsubprocessを使う方法は弊社ブログに詳しい記事がありますのでそちらをご確認ください。
subprocessからlhaを呼び出す方法ですが、lhaをインストールする必要がありますのでbrewではじめにインストールしておきます。またバージョンとオプションも確認しておきます。
$ brew install lha $ lha --version LHa for UNIX version 1.14i-ac20050924p1 (i686-apple-darwin19.6.0) $ lha --help LHarc for UNIX V 1.02 Copyright(C) 1989 Y.Tagawa LHx for MSDOS V C2.01 Copyright(C) 1990 H.Yoshizaki LHx(arc) for OSK V 2.01 Modified 1990 Momozou LHa for UNIX V 1.00 Copyright(C) 1992 Masaru Oki LHa for UNIX V 1.14 Modified 1995 Nobutaka Watazaki LHa for UNIX V 1.14i Modified 2000 Tsugio Okamoto Autoconfiscated 2001-2005 Koji Arai usage: lha [-]<commands>[<options>] [-<options> ...] archive_file [file...] commands: [axelvudmcpt] options: [q[012]vnfto[567]dizg012e[w=<dir>|x=<pattern>]] long options: --system-kanji-code={euc,sjis,utf8,cap} --archive-kanji-code={euc,sjis,utf8,cap} --extract-broken-archive --help --version commands: options: a Add(or replace) to archive q{num} quiet (num:quiet mode) x,e EXtract from archive v verbose l,v List / Verbose List n not execute u Update newer files to archive f force (over write at extract) d Delete from archive t FILES are TEXT file m Move to archive (means 'ad') o[567] compression method (a/u/c) c re-Construct new archive d delete FILES after (a/u/c) p Print to STDOUT from archive i ignore directory path (x/e) t Test file CRC in archive z files not compress (a/u/c) g Generic format (for compatibility) or not convert case when extracting 0/1/2 header level (a/u/c) e TEXT code convert from/to EUC w=<dir> specify extract directory (x/e) x=<pattern> eXclude files (a/u/c)
PythonでLZHを展開したいのですが、その際に「上書き」と「サブディレクトリに展開」を行いたいので以下のコマンドを使います。
$ lha x -f -w={サブディレクトリ名} {展開するLZHファイル名}
これをsubprocessで呼び出すPythonスクリプトを書くと以下の様になります。
後々にPythonスクリプトでエラーハンドリング等を行いため標準出力、標準エラー出力を取得できるようにしてあります。
import subprocess from subprocess import PIPE extract_dir = "zipcodes" # サブディレクトリ名 target_file = "zipcode.lzh" # 展開するLZHファイル名 proc = subprocess.run( "lha x -f -w={} {}".format(extract_dir, target_file), shell=True, stdout=PIPE, stderr=PIPE, text=True, ) print("returncode: {}".format(proc.returncode)) print("stdout: {}".format(proc.stdout)) print("stderr: {}".format(proc.stderr))
実行結果
returncode: 0 stdout: zipcodes/08IBARAK.CSV - Melting : ......................... zipcodes/08IBARAK.CSV - Melting : ooooooooooooooooooooooooo zipcodes/08IBARAK.CSV - Melted zipcodes/09TOCHIG.CSV - Melting : ................................ zipcodes/09TOCHIG.CSV - Melting : oooooooooooooooooooooooooooooooo zipcodes/09TOCHIG.CSV - Melted zipcodes/10GUMMA.CSV - Melting : ............................ zipcodes/10GUMMA.CSV - Melting : oooooooooooooooooooooooooooo zipcodes/10GUMMA.CSV - Melted zipcodes/11SAITAM.CSV - Melting : .......................... zipcodes/11SAITAM.CSV - Melting : oooooooooooooooooooooooooo zipcodes/11SAITAM.CSV - Melted zipcodes/12CHIBA.CSV - Melting : ............................... zipcodes/12CHIBA.CSV - Melting : ooooooooooooooooooooooooooooooo zipcodes/12CHIBA.CSV - Melted zipcodes/13TOKYO.CSV - Melting : ................................ zipcodes/13TOKYO.CSV - Melting : oooooooooooooooooooooooooooooooo zipcodes/13TOKYO.CSV - Melted zipcodes/14KANAGA.CSV - Melting : ....................... zipcodes/14KANAGA.CSV - Melting : ooooooooooooooooooooooo zipcodes/14KANAGA.CSV - Melted stderr:
Lhafileモジュールで操作する
次にPythonモジュールの Lhafile を使ってみます。
GitHub - FrodeSolheim/python-lhafile: LHA archive support for Python
$ pip install lhafile $ pip list |grep lhafile lhafile 0.3.0
Lhafileの使い方は、lhafile.Lhafile()
でLZHファイルを読み込みます。あとは組み込み関数のopen()
と同様にf.read()
でLZHファイル内のファイルを読み込んで操作します。
import lhafile import os f = lhafile.Lhafile("zipcode.lzh") extract_dir = "zipcodes" # サブディレクトリ名 os.makedirs(extract_dir, exist_ok=True) for info in f.infolist(): fname = info.filename with open("{}/{}".format(extract_dir, fname), "wb") as tf: tf.write(f.read(info.filename))
上記のスクリプトを実行した結果LZHファイルの中身がzipcodes
ディレクトリに展開されます。
zipcodes/ ├── 08IBARAK.CSV ├── 09TOCHIG.CSV ├── 10GUMMA.CSV ├── 11SAITAM.CSV ├── 12CHIBA.CSV ├── 13TOKYO.CSV └── 14KANAGA.CSV
また応用的な使い方として、展開されたファイルの中身を直接pandas.DataFrameとして扱うこともできます。
import lhafile import pandas as pd from io import BytesIO f = lhafile.Lhafile("zipcode.lzh") for info in f.infolist(): fname = info.filename df = pd.read_csv(BytesIO(f.read(fname))) print(df)
実行結果
14101 230 2300000 カナガワケン ヨコハマシツルミク ... 0.1 0.2 0.3 0.4 0.5 0 14101 230 2300033 カナガワケン ヨコハマシツルミク ... 0 1 0 0 0 1 14101 230 2300035 カナガワケン ヨコハマシツルミク ... 0 1 0 0 0 2 14101 230 2300021 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0 3 14101 230 2300024 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0 4 14101 230 2300022 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0 ... ... ... ... ... ... ... .. .. .. .. ... 2293 14401 24303 2430308 カナガワケン アイコウグンアイカワマチ ... 0 0 0 0 0 2294 14402 24301 2430100 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0 2295 14402 257 2570061 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0 2296 14402 24301 2430112 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0 2297 14402 24301 2430111 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0 [2298 rows x 15 columns]
まとめ
今ではほぼ見なくなったLZHで圧縮されたファイルをPythonで展開してみました。需要は少ないと思いますがどなたかの参考になれば幸いです。
最後まで読んで頂いてありがとうございました。